﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using PlayerIOClient;

public enum PlayerColor {
	Blue = 0,
	Red = 1
};

public enum GameType {
	Local = 0,
	Host = 1, //Create Game
	Guest = 2 //Join Game
};

public class GameManager : MonoBehaviour {

	static public GameType gameType = GameType.Local;
	
	//public GameHex hexPrefab;
	public GameBoard gameBoard;
	public Transform gameCamera; //camera look-at
	public GUIHexManager localManager;
	public GUIHexManager remoteManager; //TEMP- replace with network

	//public int boardSize; 	//rows in board
	public int handSize;	//visible pieces
	public int numPieces;
	public int minValue;
	public int maxValue;

	public int maxScore;

	public Vector2 offsetRight;

	public Transform BubbleEffect;
			
	// Private members
	private class Piece { // a game unit in-game (with color)
		public PlayerColor color;
		public GamePowerUp powerUp;
		public GameUnit unit;
		public GameHex hex;
		public bool locked;
		public int x = -1;
		public int y = -1;
		public int level = 0; //modifier
		public int shield = 0; //current
		public int powerTimer = 0; //timer for powerups
		public Transform powerFX;
	}

	private GamePool loadedPool = null;
	private GameField loadedField = null;
	
	private Piece[][] board; //logic state of the game board
	private int boardCount = 0; //number of pieces in board
	private List<Piece> pieces; //active units list
	private List<Piece> localPieces; //container of units
	private List<Piece> remotePieces; //container of units
	private PlayerColor localPlayer = PlayerColor.Blue;
	private PlayerColor remotePlayer = PlayerColor.Red;
	private PlayerColor curPlayer = PlayerColor.Blue;

	private Piece capturedPiece = null; //apply powerups on played piece
	private List<Piece> timedPieces = new List<Piece>(); //piece with temporary effects, eg. bubble

	private bool gameStarted = false;	
	private bool isRemotePlayerReady = false;
	
	private Matrix4x4 matGameToWorld;
	
	private Vector2[] neighbourOffsets;

	// Use this for initialization
	void Start () {
		enabled = false; //wait for onGameStart
		localPieces = new List<Piece>();
		remotePieces = new List<Piece>();
		
		GameEvents.GameStart += OnGameStart;
		GameEvents.GameOver += OnGameOver;

		// allocate space to store the pieces
		/*int boardSize = gameBoard.boardSize;
		board = new Piece[boardSize][];
		for (int i=0; i<boardSize; i++)
			board[i] = new Piece[boardSize];*/
		
		// load board
		GameFieldSet playerFields;
		GameSaveLoad.LoadFields(out playerFields);
		GameFieldSet.playerFields = playerFields;
		loadedField = GameFieldSet.activeField;
		
		neighbourOffsets = new Vector2[6];
		neighbourOffsets [0] = new Vector2 (1,0); //side:1
		neighbourOffsets [1] = new Vector2 (0,1); //side:1
		neighbourOffsets [2] = new Vector2 (-1,1); //side:2
		neighbourOffsets [3] = new Vector2 (-1,0); //side:2
		neighbourOffsets [4] = new Vector2 (0,-1); //side:3
		neighbourOffsets [5] = new Vector2 (1,-1); //side:3
	}

	void OnDestroy() {
		GameEvents.GameStart -= OnGameStart;
		GameEvents.GameOver -= OnGameOver;
	}

	// Update is called once per frame
	void Update () {
		// Multiplayer game- input only during my turn, when the other player is connected
		if (gameType != GameType.Local) {
			if (!isRemotePlayerReady)
				return;
			
			if (localPlayer != curPlayer)
				return;
		}

		// move a selected piece (teleport)
		if (capturedPiece != null) {
			//--------------------
			Vector2 gamePos;
			if (gameBoard.GetTouchInput( out gamePos )) {
				int x = Mathf.FloorToInt(gamePos.x);
				int y = Mathf.FloorToInt(gamePos.y);
				//--------------------	
				if (CheckValidMovement( capturedPiece, x,y )) {
					//remove from logic board
					TakePiece(capturedPiece);
					PlaceAndAttack(capturedPiece, x, y); //place in logic board
					gameBoard.MoveHex(capturedPiece.hex.transform, gamePos); //update in game
					
					GameUnit curUnit = capturedPiece.unit;
					capturedPiece = null;
					
					if (gameType != GameType.Local)
						SendMoveUnit(localPlayer, curUnit.level, x, y, curUnit.skill, curUnit.skillLevel); //send to remote
					
					OnUnitMoved( null ); //change turn
				}
			}
			
			return;
		}
		
		// allow selecting a piece
		if (GUIHexManager.current.CheckSelectHex())
			return;
		
		//selected piece?
		GUIHex guiHex = GUIHexManager.current.getSelectedHex ();
		if (guiHex != null) {
			//--------------------
			Vector2 gamePos;
			if (gameBoard.GetTouchInput( out gamePos )) {
				int x = Mathf.FloorToInt(gamePos.x);
				int y = Mathf.FloorToInt(gamePos.y);
				//--------------------	
				GameUnit curUnit = guiHex.getUnit();
				if (curUnit != null)
				{
					Piece curPiece = findPieceByUnit(curUnit);
					if (RequestMoveUnit(curPiece, x, y)) {
						//----------------------------------------
						GUIHexManager.current.removeHex( guiHex ); //remove from gui
						GameUI.instance.hideMessage();
						
						if (gameType != GameType.Local) // send network command
							SendMoveUnit(localPlayer, curUnit.level, x, y, curUnit.skill, curUnit.skillLevel);
						
						OnUnitMoved( curUnit ); // check switch turn
					}
				}
				else {
					GamePowerUp powerUp = guiHex.getPowerUp();
					if (RequestLaunchPowerUp( powerUp, x, y )) {
						//----------------------------------------
						GUIHexManager.current.removeHex( guiHex ); //remove from gui
						GameUI.instance.hideMessage();
						
						if (gameType != GameType.Local) // send network command
							SendLaunch( localPlayer, powerUp.power, powerUp.level, x, y);
						
						OnPowerUpLaunched(powerUp); // check switch turn
					}
				}
			}
		}
	}
	
	void OnGameStart() {
		Debug.Log ("GameManager.OnGameStart");
		
		// initialize visual, logic board
		if (gameType == GameType.Local || gameType == GameType.Host) {
			InitBoard();
		}
		
		// initialize visitor player (for local games)
		if (gameType == GameType.Local) {
			InitLocalPlayer();
			InitRemotePlayer();
			switchTurn (PlayerColor.Blue); //start game immediately
		}
		else {
			localManager.setActivePlayer (false);
			InitSession(); //and wait for other users
		}
		
		GameUI.instance.showScore( localManager.playerScore, remoteManager.playerScore );
		
		gameStarted = true;
		enabled = true;
	}

	void OnGameOver() {
		Debug.Log ("GameManager.OnGameOver");
		
		if (!gameStarted) return;
		isRemotePlayerReady = false;
		gameStarted = false;
		enabled = false;

		localManager.setActivePlayer (false);
		remoteManager.setActivePlayer (false);
	}

	void InitLocalPlayer() {
		// init local player
		localManager.maxPlayerScore = maxScore;
		localManager.setActivePlayer (true);
		localManager.clear ();
		
		pieces = localPieces;
		
		//GamePool loadedPool = null;
		GameSaveLoad.LoadPool(out loadedPool);
		if (loadedPool != null && loadedPool.teams.Count > 0) {
			//Debug.Log("GameManager.InitLocalPlayer : loadedPool");
			GamePool.playerPool = loadedPool;
			GamePool.currentTeam = loadedPool.teams[loadedPool.teamIndex];
			LoadDeck (localPlayer, GamePool.currentTeam);
		}
		else
			InitDeck (localPlayer);
	}
	
	void InitRemotePlayer() {
		remoteManager.maxPlayerScore = maxScore;
		remoteManager.setActivePlayer (true);
		remoteManager.clear ();
		
		pieces = remotePieces;
		if (loadedPool != null && loadedPool.teams.Count > 0) {
			GameTeam remoteTeam = loadedPool.teams[loadedPool.enemyTeamIndex];
			LoadDeck(remotePlayer, remoteTeam);
		}
		else
			InitDeck (remotePlayer);
		
		isRemotePlayerReady = true;
	}
	
	bool RequestMoveUnit( Piece curPiece, int x, int y )
	{
		if (CheckValidMovement( curPiece, x, y )) {
			// if it's a valid position, place a GameHex
			PlaceAndAttack( curPiece, x, y );
			
			GameHex hex = CreateHex(new Vector2(x,y)); //place in game
			hex.SetColor( curPiece.color );
			hex.setUnit( curPiece.unit );
			hex.setShield( curPiece.shield );
			curPiece.hex = hex;
			
			return true;
		}
		
		return false;
	}
	
	// call after the movement
	void OnUnitMoved( GameUnit curUnit ) {
		// change turn (unless a Freeze skill was used)
		if (curUnit != null && curUnit.skill == GameSkill.Freeze) {
			GameUI.instance.showMessage( "Play another turn" );
		}
		else {
			PlayerColor player = (curPlayer == PlayerColor.Blue) ? PlayerColor.Red : PlayerColor.Blue;
			switchTurn( player );
		}
	}
	
	// call after powerup
	void OnPowerUpLaunched( GamePowerUp powerUp ) {
		if (powerUp.power == GamePower.Teleport) {
			GameUI.instance.showMessage( "Select target" );
		}
		else {
			PlayerColor player = (curPlayer == PlayerColor.Blue) ? PlayerColor.Red : PlayerColor.Blue;
			switchTurn( player );
		}
	}
	
	public void switchTurn( PlayerColor player ) {
		if (CheckGameOver ()) {
			Debug.Log("GameManager.switchTurn : Player=" + player);
			GameEvents.TriggerGameOver ();
			
			if (gameType != GameType.Local) //notify all users
				SendGameOver();
		}
		else
		{
			curPlayer = player;
			
			if (gameType == GameType.Local) {
				if (curPlayer == localPlayer) {
					remoteManager.setActivePlayer (false);
					localManager.setActivePlayer (true);
					pieces = localPieces;
				} else {
					localManager.setActivePlayer (false);
					remoteManager.setActivePlayer (true);
					pieces = remotePieces;
				}
			}
			// Online play
			else if (curPlayer == localPlayer) {
				localManager.setActivePlayer(true);
				remoteManager.setActivePlayer(false);
				pieces = localPieces;
			}
			else {
				//localManager.setActivePlayer(false);
				GameUI.instance.showMessage("switchTurn(" + curPlayer + ") Waiting for remote player");
			}
			
			// advance timed events
			List<Piece> currentPieces = new List<Piece>( timedPieces );
			foreach (Piece piece in currentPieces) {
				if (piece.powerUp != null) {
					if (piece.powerTimer > 0) {
						piece.powerTimer--;
						
						if (piece.powerTimer == 0) {
							piece.powerUp = null;
							timedPieces.Remove( piece );
							
							if (piece.powerFX != null) { // destroy visual effect
								Destroy( piece.powerFX.gameObject );
								piece.powerFX = null;
							}
						}
					}	
				}
				else if (piece.unit.skill != GameSkill.None) {
					//temporary skills, eg. Trap (invisible), 
				}
			}
		}
		
		// refresh player names, current turn
		string player1 = "Local:" + localPlayer;
		string player2 = "Remote:" + remotePlayer;
		if (curPlayer == localPlayer)
			player1 += " (current)";
		else
			player2 += " (playing)";
		
		if (localPlayer == PlayerColor.Blue)
			GameUI.instance.showPlayers( player1, player2 );
		else
			GameUI.instance.showPlayers( player2, player1 );
	}

	//--------------------------
	// Online
	//--------------------------
	
	//private memebers
	private Connection connection;
	private bool joinedRoom = false;
	private List<PlayerIOClient.Message> msgList = new List<PlayerIOClient.Message>(); //  Messsage queue implementation
	
	void InitSession() {
		//System.Random random = new System.Random();
		//string userid = "Guest" + random.Next(0, 10000);
		string userid = SystemInfo.deviceName;
		#if UNITY_EDITOR
		userid += "_editor";
		#endif
		
		Debug.Log ("GameManager.startGame");
		
		PlayerIOClient.PlayerIO.Connect(
			"hexagon-vplkybteeynoinxqznipa",// Game id (Get your own at playerio.com. 1: Create user, 2:Goto admin pannel, 3:Create game, 4: Copy game id inside the "")
			"public",						// The id of the connection, as given in the settings section of the admin panel. By default, a connection with id='public' is created on all games.
			userid,							// The id of the user connecting. This can be any string you like. For instance, it might be "fb10239" if you´re building a Facebook app and the user connecting has id 10239
			null,							// If the connection identified by the connection id only accepts authenticated requests, the auth value generated based on UserId is added here
			null,
			null,
			delegate(Client client)
			{
				Debug.Log("Successfully connected to Player.IO");
				GameUI.instance.showMessage("Successfully connected to Player.IO");
				
				//target.transform.Find("NameTag").GetComponent<TextMesh>().text = userid;
				//target.transform.name = userid;
				
				// Uncoment the line below to use the Development Server
				//client.Multiplayer.DevelopmentServer = new ServerEndpoint("localhost", 8184);
				
				if (gameType == GameType.Host)
				{
					// Create and join the room 
					Dictionary<string, string> RoomData = new Dictionary<string, string>();
					RoomData.Add("teamSize", localPieces.ToString());
					RoomData.Add("boardSize", loadedField.size.ToString());
					string lockedCells = "";
					foreach (int index in loadedField.lockedCells) {
						if (lockedCells.Length > 0)
							lockedCells = string.Concat(lockedCells, ",");
						lockedCells = string.Concat(lockedCells, string.Format("{0}", index));
					}
					RoomData.Add("lockedCells", lockedCells);
					
					client.Multiplayer.CreateJoinRoom(
						null,		//"OmniRoom", //Room id. If set to null a random roomid is used
						"Omnihex",	//The room type started on the server
						true,		//Should the room be visible in the lobby?
						RoomData,	//null, //Room Data=boardsize=X,lockedcells=[X,X,...]
						null,		//Join Data
						delegate(Connection connection)
						{
							Debug.Log("Created Room.");
							GameUI.instance.showMessage("Created Room.");
							//infomsg = "Joined Room.";
							joinedRoom = true;
							
							//onOpenConnection(connection)
							// We successfully joined a room so set up the message handler
							this.connection = connection;
							this.connection.OnMessage += onHandleMessage;
							this.connection.OnDisconnect += onDisconnect;
						},
						delegate(PlayerIOError error) {
							Debug.Log("Error Joining Room: " + error.ToString());
							GameUI.instance.showMessage( error.ToString() );
							joinedRoom = false;
						}
					);
				}
				else if (gameType == GameType.Guest)
				{
					client.Multiplayer.ListRooms(
						"Omnihex",
						null,
						10,
						0,
						delegate(RoomInfo[] roomInfo) {
							// list all rooms
							foreach (RoomInfo info in roomInfo) {
								if (info.OnlineUsers == 1) {
									//get game parameters from room data
									loadedField = LoadField( info.RoomData );
									InitBoard( );
									
									//connect to the first available room
									client.Multiplayer.JoinRoom(
										info.Id, //roomId
										null, //joinData, TODO: send deck size
										delegate(Connection connection) {
											this.connection = connection;
											this.connection.OnMessage += onHandleMessage;
											this.connection.OnDisconnect += onDisconnect;
											joinedRoom = true;
										},
										delegate(PlayerIOError error) {
											Debug.Log("Error Joining Room: " + error.ToString());
											GameUI.instance.showMessage( error.ToString() );
										}
									);

									break;
								}
							}
							
							if (!joinedRoom) {
								GameUI.instance.showMessage("No Available Games!");
								GameEvents.TriggerGameOver();
							}
						}
					);
				}
			},
			delegate(PlayerIOError error) {
				Debug.Log("Error connecting: " + error.ToString());
				GameUI.instance.showMessage( error.ToString() );
				
				if (gameStarted)
					GameEvents.TriggerGameOver();
			}
		);
	}

	void SendGameOver() {
		connection.Send("GameOver");
	}

	void SendMoveUnit( PlayerColor color, int value, int x, int y, GameSkill skill, int skillLevel ) {
		connection.Send("MoveUnit", (int)color, value, x, y, (int)skill, skillLevel);
	}
	
	void SendLaunch( PlayerColor color, GamePower power, int level, int x, int y ) {
		connection.Send("LaunchPowerup", (int)color, (int)power, level, x, y);
	}

	void onHandleMessage(object sender, PlayerIOClient.Message m) {	
		msgList.Add( m );
	}
	
	void onDisconnect(object sender, string message) {
		if (joinedRoom) {
			joinedRoom = false;
			GameUI.instance.showMessage("Player.IO OnDisconnect");
			GameEvents.TriggerGameOver();
		}
	}
	
	void FixedUpdate() {
		foreach (PlayerIOClient.Message msg in msgList) {
			GameUI.instance.showMessage("HandleMessage: Type=" + msg.Type);
			
			switch (msg.Type) {
			case "PlayerJoined":
				//check that we're not playing yet
				if (!isRemotePlayerReady) {
					remotePlayer = (PlayerColor) msg.GetInt(1);
					localPlayer = (remotePlayer == PlayerColor.Blue) ? PlayerColor.Red : PlayerColor.Blue;
					
					GameUI.instance.showMessage("HandleMessage: Type=" + msg.Type + ",local=" + localPlayer + ",remote=" + remotePlayer);
					Debug.Log("HandleMessage: local=" + localPlayer + ",remote=" + remotePlayer);
					
					InitLocalPlayer();
					InitRemotePlayer();
					
					switchTurn( PlayerColor.Blue ); //host always starts
				}
				break;
			
			case "PlayerLeft":
				GameEvents.TriggerGameOver();
				break;
				
			case "GameOver":
				GameEvents.TriggerGameOver();
				break;
				
			case "MovedUnit":
			case "LaunchedPowerup":
				GameUI.instance.showMessage("HandleMessage: Type=" + msg.Type + ",player=" + msg.GetString(0));
				//play unit from other player
				bool isError = true;
				if (curPlayer != localPlayer) {
					if (curPlayer == (PlayerColor)msg.GetInt(1)) {
						if (msg.Type == "MovedUnit") {
							int x = msg.GetInt(3); //x
							int y = msg.GetInt(4); //y
								
							if (capturedPiece != null) //teleport
							{
								//---------------------
								TakePiece(capturedPiece); //logic remove
								PlaceAndAttack( capturedPiece, x, y); //update logic
								
								gameBoard.MoveHex( capturedPiece.hex.transform, new Vector2(x,y) ); //update in game
								
								OnUnitMoved( null ); //change turn
								capturedPiece = null;
								
								GameUI.instance.hideMessage();
								isError = false;
							}
							else
							{
								//---------------------
								GameUnit curUnit = new GameUnit();
								curUnit.level = msg.GetInt(2); //level
								curUnit.skill = (GameSkill)msg.GetInt(5); //skill
								curUnit.skillLevel = msg.GetInt(6); //skill level
									
								Piece curPiece = newPiece( curUnit );
								curPiece.color = curPlayer; //color
								
								if (RequestMoveUnit( curPiece, x, y )) {
									OnUnitMoved( curPiece.unit ); // check if change turn
									
									GameUI.instance.hideMessage();
									isError = false;
								}
							}
						}
						else if (msg.Type == "LaunchedPowerup")
						{
							//---------------------
							GamePowerUp powerUp = new GamePowerUp();
							powerUp.power = (GamePower)msg.GetInt(2); //power
							powerUp.level = msg.GetInt(3); //level
							
							int x = msg.GetInt(4);
							int y = msg.GetInt(5);
							
							if (RequestLaunchPowerUp( powerUp, x, y )) {
								OnPowerUpLaunched( powerUp );
								
								GameUI.instance.hideMessage();
								isError = false;
							}
						}
					}
					
				}

				if (isError) {
					GameUI.instance.showMessage("Player.IO : Invalid command: player=" + curPlayer);
					GameEvents.TriggerGameOver();
				}
				
				break;
			}
			
			
				GameUI.instance.showMessage("HandleMessage: Type=" + msg.Type + ",player=" + msg.GetString(0));

		}
		
		msgList.Clear();
	}
	
	//--------------------------
	// Private Methods
	//--------------------------
	GameField LoadField( Dictionary<string, string> RoomData ) {
		GameField field = new GameField();
		field.size = int.Parse(RoomData["boardSize"]);
		string[] lockedCells = RoomData["lockedCells"].Split(","[0]);
		foreach (string cell in lockedCells)
			field.lockedCells.Add( int.Parse(cell) );
			
		return field;
	}
	
	void InitBoard() {
		ClearBoard();
	
		// initialize visual board
		gameBoard.boardSize = loadedField.size;
		gameBoard.Load(); //create cells
		foreach (int cell in loadedField.lockedCells)
			//gameBoard.SetCellLocked(cell, true);
			gameBoard.SetCellVisible(cell, false);
		
		// allocate space to store the pieces
		int boardSize = gameBoard.boardSize;
		board = new Piece[boardSize][];
		for (int i=0; i<boardSize; i++)
			board[i] = new Piece[boardSize];
			
		foreach (int cellIndex in loadedField.lockedCells) {
			int x = cellIndex / loadedField.size;
			int y = cellIndex % loadedField.size;
			Piece piece = new Piece();
			piece.x = x; piece.y = y;
			piece.locked = true;
			board[x][y] = piece;
		}
		
		//find center of the board to adjust camera position
		/*for (int x = 0; x < boardSize; x++)
			for (int y = 0; x < boardSize; x++)
				;*/
	}
	
	void ClearBoard() {
		if (board == null) return;
		int width = board.Length;
		for (int x=0; x<width; x++) {
			int height = board[x].Length;
			for (int y=0; y<height; y++) {
				if (board[x][y] != null && board[x][y].hex != null) {
					Destroy(board[x][y].hex.gameObject);
					board[x][y].hex = null;
				}
			}
		}
	}
	
	//initialize a random user Deck for this game
	void InitDeck( PlayerColor playerColor ) {
		// create hand pieces
		pieces.Clear ();
		for (int pIndex = 0; pIndex< numPieces; pIndex++) {
			Piece piece = new Piece();
			//piece.index = pIndex;
			piece.color = playerColor;
			piece.unit = new GameUnit();
			piece.unit.level = pIndex+1;
			pieces.Add(piece);
			
			// create hex in hand
			GUIHex guiHex = GUIHexManager.current.createHex();
			guiHex.setColor( playerColor );
			guiHex.setUnit( piece.unit );
		}
		
		GUIHexManager.current.maxHandSize = handSize;
		GUIHexManager.current.Shuffle();
	}
	
	void LoadDeck( PlayerColor playerColor, GameTeam team ) {
		foreach (GameUnit unit in team.units) {
			Piece piece = newPiece( unit );
			piece.color = playerColor;
			pieces.Add (piece);
			
			GUIHex guiHex = GUIHexManager.current.createHex();
			guiHex.setColor(playerColor);
			guiHex.setUnit(unit);
		}
		
		foreach (GamePowerUp powerup in team.powerups) {
			Piece piece = new Piece();
			piece.color = playerColor;
			piece.powerUp = powerup;
			pieces.Add(piece);
			
			GUIHex guiHex = GUIHexManager.current.createHex();
			guiHex.setColor(playerColor);
			guiHex.setPowerUp(powerup);
		}
		
		GUIHexManager.current.maxHandSize = handSize;
		GUIHexManager.current.Shuffle();
	}
	
	Piece newPiece(GameUnit unit) {
		Piece piece = new Piece();
		piece.unit = unit;
				
		switch (unit.skill) { //shield by skill
		case GameSkill.Shield:
			piece.shield += unit.skillLevel;
			break;
		case GameSkill.Wall:
			piece.shield += unit.skillLevel - 1;
			break;
		}
		
		return piece;
	}
	
	Piece findPieceByUnit(GameUnit unit) {
		foreach (Piece piece in pieces)
			if (piece.unit == unit)
				return piece;
		
		return null;
	}
	
	bool IsValidPosition( Vector2 gamePos ) {
		int boardSize = gameBoard.boardSize;
		if (gamePos.x >= 0 && gamePos.x < boardSize && gamePos.y >= 0 && gamePos.y < boardSize) {
			// check locked cells
			int x = Mathf.FloorToInt( gamePos.x );
			int y = Mathf.FloorToInt( gamePos.y );
			Piece piece = board[x][y];
			if (piece != null && piece.locked)
				return false;
			
			return true;
		}
		
		return false;
	}

	GameHex CreateHex( Vector2 gamePos ) {
		Transform hexTransform = gameBoard.CreateHex( gamePos );
		return hexTransform.GetComponent<GameHex>();
	}
	
	void DestroyHex( GameHex hex ) {
		//Destroy( hex.gameObject );
		hex.fadeOut();
	}

	bool CheckValidMovement(Piece curPiece, int x, int y) {
		if (!IsValidPosition( new Vector2(x,y) )) {
			return false;
		}
	
		Piece target = board[x][y];
		if (target == null) {
			// check for an active bubble
			if (CheckNeighbourBubble(x,y, curPlayer)) {
				return false;
			}
			
			// empty board: all positions are ok
			/*if (boardCount == 0)
				return true;
	
			// check if there is another piece on all 6 directions
			for (int i=0; i<6; i++) {
				Vector2 gamePos = new Vector2(x,y) + neighbourOffsets[i];
				if (IsValidPosition( gamePos )) {
					int px = Mathf.FloorToInt(gamePos.x);
					int py = Mathf.FloorToInt(gamePos.y);
					if (board[px][py] != null)
						return true;
				}
			}*/
			
			//any other slot is ok
			return true;
		}
		
		//units that can be played on top of others
		switch (curPiece.unit.skill) {
		case GameSkill.Crush:
			if (target.color != curPiece.color) { //play only on enemy units
				if (target.shield == 0) // without shield
					if (CheckRequiredLevel(curPiece.unit.skillLevel, target.level )) {
						return true;
					}
			}
			break;
		}
		
		return false;
	}
	
	void PlaceAndAttack( Piece curPiece, int x, int y ) {
		Piece target = board[x][y];
		if (target != null) {
			// units that can be played on top of another destroy them
			int damage = 1;
			target.hex.showScore(1); //+1
			ApplyDamage(damage);
			
			TakePiece(target); //remove from logic board
			DestroyHex(target.hex); //remove from in-gmae
			
			Debug.Log ("PlaceAndAttack : crushed existing piece!");
		}
	
		board[x][y] = curPiece; //place in board
		curPiece.x = x;
		curPiece.y = y;
		boardCount++;
		
		CheckAttackHex(x,y);
	}
	
	void TakePiece( Piece curPiece ) {
		board [curPiece.x][curPiece.y] = null;
		boardCount--;
		curPiece.x = curPiece.y = -1;
	}
	
	private Piece attackPiece = null;
	
	void CheckAttackHex( int x, int y ) {
		Piece curPiece = board[x][y]; // piece on this square
		if (curPiece == null)
			return;

		bool isAttackPiece = false; // piece that started the attack
		if (attackPiece == null) {
			attackPiece = curPiece;
			isAttackPiece = true;
		}
		
		int damage = 0;
		for (int i=0; i<6; i++) {
			Vector2 gamePos = new Vector2(x,y) + neighbourOffsets[i];
			if (!IsValidPosition( gamePos ))
				continue;
			
			int px = Mathf.FloorToInt(gamePos.x);
			int py = Mathf.FloorToInt(gamePos.y);
			Piece neighbourPiece = board[px][py];
			if (neighbourPiece != null) {
				if (neighbourPiece.color != curPiece.color) { 
					if (neighbourPiece.unit.skill == GameSkill.Wall) { //walls resist regular attacks
						continue;
					}
					
					//int attackSide = i/2;
					//int attackValue = piece.values[attackSide]; //side
					int attackValue = curPiece.unit.level + curPiece.level; //unit level + modifier
					// compare numbers on the side:
					//int defenseSide = ((i+3)%6)/2;
					//int defenseValue = values[defenseSide]; //side
					int defenseValue = neighbourPiece.unit.level + neighbourPiece.level; //unit level + modifier
					// change color and take damage
					//Debug.Log("CheckAttackHex : ATTACK side=" + attackSide + ", value=" + attackValue);
					//Debug.Log("CheckAttackHex : DEFENSE side=" + defenseSide + ", value=" + defenseValue);
					if (attackValue > defenseValue) {
						if (neighbourPiece.shield > 0)
						{
							neighbourPiece.shield--;
							neighbourPiece.hex.setShield( neighbourPiece.shield );
						}
						else {
							damage++;
							neighbourPiece.color = curPiece.color;
							neighbourPiece.hex.SetColor( curPiece.color );
							neighbourPiece.hex.showScore(1);
							
							//chain attack- recursively attack neighbours
							if (attackPiece.unit.skill == GameSkill.Chain) {
								CheckAttackHex(px, py); //TODO- limit by skill level
							}
						}
					}
					else if (isAttackPiece) { // to avoid re-apply drain during chain
						if (curPiece.unit.skill == GameSkill.Drain) { //reduce neighbours number
							neighbourPiece.level -= curPiece.unit.skillLevel;
							neighbourPiece.hex.setLevelModifier( neighbourPiece.level );
						}
					}
				}
				else if (isAttackPiece) { // to avoid re-applying boost during chain
					if (curPiece.unit.skill == GameSkill.Boost) { //increase neighbours number
						neighbourPiece.level += curPiece.unit.skillLevel;
						neighbourPiece.hex.setLevelModifier( neighbourPiece.level );
					}
				}
			}
		}
		
		if (isAttackPiece) {
			attackPiece = null;
		}
		
		ApplyDamage( damage );
	}
	
	bool CheckNeighbourBubble( int x, int y, PlayerColor curColor ) {
		Piece piece = board[x][y];
		if (piece != null && piece.powerUp != null && piece.powerUp.power == GamePower.Bubble) {
			return true;
		}
	
		for (int i=0; i<6; i++)
		{
			Vector2 gamePos = new Vector2(x,y) + neighbourOffsets[i];
			if (!IsValidPosition( gamePos ))
				continue;
			
			int px = Mathf.FloorToInt(gamePos.x);
			int py = Mathf.FloorToInt(gamePos.y);
			Piece neighbourPiece = board[px][py];
			
			if (neighbourPiece != null) {
				if (neighbourPiece.color != curColor) { 
					if (neighbourPiece.powerUp != null &&
					    neighbourPiece.powerUp.power == GamePower.Bubble)
					    return true;
				}
			}
		}
		
		return false;
	}
	
	bool RequestLaunchPowerUp( GamePowerUp powerUp, int x, int y ) {
		if (CheckValidPowerUp(powerUp, x, y))
		{
			Debug.Log("RequestLaunchPowerUp : powerUp="+powerUp.power+",x="+x+",y="+y);
			LaunchPowerUp(powerUp, x, y); //apply effects
			return true;
		}
		
		return false;
	}
	
	// simple criteria to limit targets of powerups / skills
	bool CheckRequiredLevel(int skillLevel, int targetLevel) {
		if (targetLevel > ((skillLevel * 2) + 1)) {
			int minSkillLevel = targetLevel / 2;
			GameUI.instance.showMessage( "Requires powerup level: " + minSkillLevel);
			return false;
		}
		
		return true;
	}
	
	bool CheckValidPowerUp(GamePowerUp powerUp, int x, int y) {
		if (powerUp == null)
			return false;
		
		Piece target = board[x][y];
		
		// powerups played on empty slots
		if (target == null) {
			//if (powerUp.power == GamePower.xxx)
			//	return true;
			
			GameUI.instance.showMessage( "No target unit in position: ("+x+","+y+")" );
			return false;
		}
		
		// powerups can't be played inside an enemy bubble
		if (CheckNeighbourBubble(x, y, curPlayer)) {
			GameUI.instance.showMessage( "Found enemy bubble!" );
			return false;
		}
		
		// powerups are played on another piece
		switch (powerUp.power) {
		case GamePower.Replay:
		case GamePower.Teleport:
		case GamePower.Bubble:
			// powerups launched on same color
			if (target.color != curPlayer) {
				GameUI.instance.showMessage("Not one of your units!");
				return false;
			}
			break;
			
		default: //powerups launched on different color
			if (target.color == curPlayer) {
				GameUI.instance.showMessage("Not an enemy unit!");
				return false;
			}
			break;
		}
		
		// further requirements, eg. level
		bool maxTargetLevel = false;
		if (target.color != curPlayer)
			maxTargetLevel = true;
		else {
			if (powerUp.power == GamePower.Replay)
				maxTargetLevel = true;
		}
		
		if (maxTargetLevel) {
			if (!CheckRequiredLevel( powerUp.level, target.unit.level + target.level ))
				return false;
				
			Debug.Log("CheckRequiredLevel powerup=" + powerUp.level + ", target=" + target.level);
		}
		
		return true;
	}
		
	//result: completed turn
	void LaunchPowerUp(GamePowerUp powerUp, int x, int y) {
		// visual effect: create a hex on that position
		GameHex hex = CreateHex(new Vector2(x,y)); //place in game
		hex.SetColor( curPlayer );
		hex.setPowerUp( powerUp );
		hex.fadeOut();
		
		// apply effect
		Piece target = board[x][y];
		int damage = 0;
		
		switch (powerUp.power) {
		case GamePower.Destroy:
			if (target.shield > 0) {
				target.shield--;
				target.hex.setShield(target.shield);
			}
			else {
				target.hex.showScore( 1 ); //+1
				DestroyHex( target.hex );
				target.x = target.y = -1;
				board[x][y] = null;
				damage++;
			}
			break;
			
		case GamePower.Replay:
			CheckAttackHex(x,y);
			break;
			
		case GamePower.Teleport:
			capturedPiece = target;
			break;
			
		case GamePower.TakeOver:
			damage++;
			target.color = curPlayer;
			target.hex.showScore( 1 ); //+1
			target.hex.SetColor( curPlayer );
			CheckAttackHex(x,y); //attack using enemy piece
			break;
			
		case GamePower.Bubble:
			target.powerUp = powerUp;
			target.powerTimer = powerUp.level * 2; //duration in player turns
			timedPieces.Add(target);
		Debug.Log("LaunchPowerup: Bubble, x="+x+",y="+y);
			target.powerFX = gameBoard.CreateEffect(new Vector2(x,y), BubbleEffect); //visual effect
			break;
		}
		
		ApplyDamage( damage );
	}
	
	//add score
	void ApplyDamage( int damage ) {
		if (damage > 0) {
			if (curPlayer == localPlayer)
				localManager.addScore( damage );
			else
				remoteManager.addScore( damage );
			
			if (localPlayer == PlayerColor.Blue)	
				GameUI.instance.showScore( localManager.playerScore, remoteManager.playerScore );
			else
				GameUI.instance.showScore( remoteManager.playerScore, localManager.playerScore );
		}
	}

	bool CheckGameOver() {
		// max score
		if (localManager.playerScore >= maxScore) {
			GameUI.instance.showVictoryMessage( localManager.playerColor );
			return true;
		}

		if (remoteManager.playerScore >= maxScore) {
			GameUI.instance.showVictoryMessage( remoteManager.playerColor );
			return true;
		}

		// no more moves
		if (localManager.getHexCount () == 0 ||
			(gameType == GameType.Local && remoteManager.getHexCount () == 0))  //TODO-
		{
			if (localManager.playerScore > remoteManager.playerScore)
				GameUI.instance.showVictoryMessage( localManager.playerColor );
			else if (remoteManager.playerScore > localManager.playerScore)
				GameUI.instance.showVictoryMessage( remoteManager.playerColor );
			else
				GameUI.instance.showMessage( "Nobody wins!" );

			return true;
		}

		return false;
	}
}
